//==================================================================
//  Copyright (C) 2006-2007  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
/// @file memfile.cpp
///
///
/// @author Davide Pasca
///
//==================================================================

#include <stdio.h>
#include "memfile.h"

//==================================================================
using namespace PUtils;

//==================================================================
#define _TRAP_FALSE(_X_)	{ if ( !(_X_) )		{ PSYS::Assert(0,_T(__FILE__),(__LINE__)); throw "Memfile operation failed"; } }


//==================================================================
static u_char *loadFile( const TCHAR *fnamep, int *fsize_retp ) throw(...)
{
	FILE	*fp;
	int		fsize, readsize;
	u_char	*datap;

	if ( _tfopen_s( &fp, fnamep, _T("rb") ) )
	{
		throw "Failed file load";
		return NULL;
	}

	fseek( fp, 0, SEEK_END );
	fsize = ftell( fp );
	if ( fsize <= 0 )
	{
		throw "Failed file load";
	}
	fseek( fp, 0, SEEK_SET );

	datap = new u_char[ fsize ];

	readsize = (int)fread( datap, 1, fsize, fp );
	if ( readsize != fsize )
	{
		delete [] datap;
		throw "Failed file load";
		return NULL;
	}

	fclose( fp );

	*fsize_retp = fsize;

	return datap;
}


//==================================================================
Memfile::Memfile( const TCHAR *fnamep ) :
		_is_static_data(true),
		_owns_static_data(true),
		_data_cur_idx(0),
		_bit_cnt(0),
		_bitstr_tmp(0)
{
	int	size;
	PDEBUG_PRINTF( _T("Loading %s ...\n"), fnamep );

	const u_char *tmpptr = loadFile( fnamep, &size );

	SetupRead( tmpptr, size );
}

//==================================================================
Memfile::~Memfile()
{
	if ( _is_static_data && _owns_static_data )
		delete [] _stadatap.begin();
}

//==================================================================
void Memfile::WriteInt( int val )
{
	WriteData( &val, sizeof(val) );
}

//==================================================================
int	Memfile::ReadInt()
{
	int	val;
	
	ReadData( &val, sizeof(val) );
	return val;

}

//==================================================================
void Memfile::WriteUInt( u_int val )
{
	WriteData( &val, sizeof(val) );
}

//==================================================================
u_int Memfile::ReadUInt()
{
	u_int	val;
	
	ReadData( &val, sizeof(val) );
	return val;
}

//==================================================================
void Memfile::WriteFloat( float val )
{
	WriteData( &val, sizeof(val) );
}

//==================================================================
float Memfile::ReadFloat()
{
	float	val;
	
	ReadData( &val, sizeof(val) );
	return val;
}

//==================================================================
void Memfile::WriteUShort( u_short val )
{
	WriteData( &val, sizeof(val) );
}

//==================================================================
u_short	Memfile::ReadUShort()
{
	u_short	val;
	
	ReadData( &val, sizeof(val) );
	return val;
}

//==================================================================
void Memfile::WriteUChar( u_char val )
{
	WriteData( &val, sizeof(val) );
}

//==================================================================
u_char Memfile::ReadUChar()
{
	u_char val;
	
	ReadData( &val, sizeof(val) );
	return val;
}

//==================================================================
void Memfile::WriteData( const void *datap, u_int size )
{
	int	needed_size = (_data_cur_idx + size) - GetDataSize();

	if ( needed_size > 0 )
	{
		// $$$ really expand ???

		if ( _is_static_data )
			_stadatap.expand( needed_size );
		else
			_dyndatap.resize( _dyndatap.size() + needed_size );
	}

	memcpy( (u_char *)GetData() + _data_cur_idx, datap, size );
	
	_data_cur_idx += size;
}

//==================================================================
void Memfile::ReadData( void *datap, u_int size )
{
	if ( _data_cur_idx + size > GetDataSize() )
		throw "Reading out of bounds !";

	memcpy( datap, _data_cur_idx + (const u_char *)GetData(), size );
	
	_data_cur_idx += size;
}

//==================================================================
void Memfile::WriteUCharArray( const std::vector<u_char> &array )
{
	WriteInt( array.size() );
	WriteData( &array.front(), array.size() );
}

//==================================================================
void Memfile::ReadUCharArray( std::vector<u_char> &array )
{
	int	len = ReadInt();
	array.resize( len );

	ReadData( &array.front(), array.size() );
}

//==================================================================
void Memfile::WriteMemfile( const Memfile *memfp )
{
	u_int	size = memfp->GetDataSize();

	WriteInt(  size );
	WriteData( memfp->GetData(), size );
}

//==================================================================
void Memfile::ReadMemfile( Memfile *memfp )
{
	int	size = ReadInt();

	if ( _data_cur_idx + size > GetDataSize() )
		throw "Reading out of bounds !";

	memfp->SeekFromStart( 0 );
	memfp->SetDataSize( 0 );
	memfp->WriteData( (u_char *)GetData() + _data_cur_idx, size );

	SeekFromCurPos( size );
}

/*
//==================================================================
bool Memfile::ReadUCharArray( std::vector<u_char> &array )
{
	int	len = ReadInt();
	_TRAP_ERR( GetError() );

	_TRAP_ERR( array.copy1( (u_char *)_data_curp, len ) );
	_data_curp += array.size_bytes();

	return true;
}
*/
//==================================================================
bool Memfile::ReadCStringPad2( char *dest_strp, int dest_str_max_len )
{
	_TRAP_FALSE( dest_str_max_len > 0 );
	// 0 terminates the string for safety !
	dest_strp[ dest_str_max_len-1 ] = 0;

	int	idx = 0;
	while (1)
	{
		u_char	ch = ReadUChar();

		_TRAP_FALSE( idx < dest_str_max_len );
		dest_strp[ idx++ ] = ch;

		if ( ch == 0 )
		{
			// see if we need padding
			if ( (idx+1) & 1 )
			{
				ch = ReadUChar();
			}
			// the only acceptable valid exit is when the 0 terminator has been
			// detected and the string didn't leak out
			return true;
		}

		if ( IsEOF() )
			return false;
	}
}

//==================================================================
void Memfile::SeekFromStart( u_int offset )
{
	if ( offset > GetDataSize() )
		throw "Seek out of bounds";

	_data_cur_idx = offset;
	_bitstr_tmp = 0;
	_bit_cnt = 0;
}

//==================================================================
void Memfile::SeekFromCurPos( int offset )
{
	int	new_pos = _data_cur_idx + offset;

	if ( new_pos < 0 || new_pos > GetDataSize() )
		throw "Seek out of bounds";

	_data_cur_idx = new_pos;
	_bitstr_tmp = 0;
	_bit_cnt = 0;
}

//==================================================================
void Memfile::SeekFromEnd( int offset )
{
	int	new_pos = GetDataSize() + offset;

	if ( new_pos < 0 || new_pos > GetDataSize() )
		throw "Seek out of bounds";

	_data_cur_idx = new_pos;
	_bitstr_tmp = 0;
	_bit_cnt = 0;
}

//==================================================================
u_int Memfile::CountBytesToEnd() const
{
	return GetDataSize() - _data_cur_idx;
}

//==================================================================
u_int Memfile::GetCurPos() const
{
	return _data_cur_idx;
}

//==================================================================
bool Memfile::IsEOF()
{
	return _data_cur_idx >= GetDataSize();
}

//==================================================================
void Memfile::WriteAlignByte()
{
	u_int	npad = _bit_cnt & 7;
	if ( npad )
	{
		_bitstr_tmp <<= npad;
		_bit_cnt += npad;
	}

	while ( _bit_cnt >= 8 )
	{
		_bit_cnt -= 8;
		WriteUChar( (u_char)(_bitstr_tmp >> _bit_cnt) );
	}
}

//==================================================================
void Memfile::ReadAlignByte()
{
	u_int	npad = _bit_cnt & 7;
	if ( npad )
	{
		_bitstr_tmp >>= npad;
		_bit_cnt -= npad;
	}
}

//==================================================================
u_int Memfile::ReadBits( int nbits )
{
	for (; _bit_cnt < nbits; _bit_cnt += 8)
	{
		u_char	newval = ReadUChar();
		_bitstr_tmp = (_bitstr_tmp << 8) | newval;
	}

	if ( _bit_cnt >= nbits )
	{
		// get the first bits
		u_int	mask = (1 << nbits) - 1;
		u_int	retval = (_bitstr_tmp >> (_bit_cnt - nbits)) & mask;

		// remove them
		_bit_cnt -= nbits;

		return retval;
	}

	PASSERT( 0 );
	return 0;
}

//==================================================================
void PUtils::Memfile::CopyFrom( const Memfile &src ) throw(...)
{
	_data_cur_idx = 0;
	_is_static_data = false;
	_bit_cnt = 0;
	_bitstr_tmp = 0;

	if ( src._is_static_data )
	{
		_dyndatap.resize( src._stadatap.len() );

		memcpy( &_dyndatap.front(),
				src._stadatap.begin(),
				src._stadatap.size_bytes() );
	}
	else
	{
		_dyndatap.resize( src._dyndatap.size() );
		copy( src._dyndatap.begin(), src._dyndatap.end(), _dyndatap.begin() );
	}
}

//==================================================================
///
//==================================================================
errno_t PUtils::mtfopen_s( FILEM ** _File, PUtils::Memfile *mfp, const TCHAR * _Mode )
{
	*_File = NULL;

	if PBADPATH( _tcsicmp( _Mode, _T("rb") ) != 0 )
	{
		return -1;
	}
	
	try {
		*_File = new FILEM( mfp );
	} catch ( ... )	{
		return -1;
	}

	return 0;
}

//==================================================================
size_t PUtils::mfread_s( void *_DstBuf, size_t _DstSize, size_t _ElementSize, size_t _Count, FILEM * _File )
{
	size_t	read_size = _ElementSize * _Count;

	if PBADPATH( read_size > _DstSize )
	{
		return 0;
	}

	try
	{
		_File->_mfp->ReadData( _DstBuf, read_size );
	}
	catch (...)
	{
		return -1;
	}

	return _Count;
}

//==================================================================
size_t PUtils::mfread( void *_DstBuf, size_t _ElementSize, size_t _Count, FILEM * _File )
{
	size_t	read_size = _ElementSize * _Count;

	try
	{
		_File->_mfp->ReadData( _DstBuf, read_size );
	}
	catch (...)
	{
		return -1;
	}

	return _Count;
}

//==================================================================
int PUtils::mfseek( FILEM * _File, long _Offset, int _Origin )
{
	try {
		switch ( _Origin )
		{
		case SEEK_SET:
			_File->_mfp->SeekFromStart( _Offset );
			break;

		case SEEK_CUR:
			_File->_mfp->SeekFromCurPos( _Offset );
			break;

		case SEEK_END:
			_File->_mfp->SeekFromEnd( _Offset );
			break;
		}
	} catch (...) {
		return -1;
	}

	return 0;
}

//==================================================================
void PUtils::mfclose( FILEM * _File )
{
	delete _File;
}

//==================================================================
int PUtils::mfgetc( FILEM * _File )
{
	return _File->_mfp->ReadUChar();
}

//==================================================================
long PUtils::mftell( FILEM * _File )
{
	return _File->_mfp->GetCurPos();
}